
#include <cegfx/cecamera.h>
#include <cegfx/ceeffect.h>
#include <cegfx/ceclipper.h>
#include <cegfx/cematerial.h>
#include <cegfx/cerendercontroller.h>
#include <cegfx/igeometry.h>
#include <cegfx/igeometryanimator.h>

using std::map;

ceRenderController::ceRenderController ()
	: device (0)
	, sort (CERS_NoSort)
{
	IQF_CONSTRUCT;
	Clear ();

}

ceRenderController::~ceRenderController ()
{
}


IQF_IMPLEMENTATION_BEGIN(ceRenderController);
IQF_IMPLEMENTATION_END ();

void ceRenderController::Clear ()
{
	nodes.clear ();
	effectNodes.clear ();
	materialNodes.clear ();
	transparentNodes.clear ();
}

void ceRenderController::DebugContent ()
{
	int numNodes = nodes.size ();
	int numTransparentNodes = transparentNodes.size ();
	int numEffectNodes = 0;
	int numMaterialNodes = 0;
	std::map<ceEffect*, ceGeometryNodeList>::iterator eit;
	for (eit = effectNodes.begin(); eit != effectNodes.end (); eit++)
	{
		numEffectNodes += eit->second.size ();
	}
	std::map<ceMaterial*, ceGeometryNodeList>::iterator mit;
	for (mit = materialNodes.begin(); mit != materialNodes.end (); mit++)
	{
		numMaterialNodes += mit->second.size ();
	}
	printf ("nodes: %d %d %d %d\n", numNodes, numEffectNodes, numMaterialNodes, numTransparentNodes);
}

void ceRenderController::SetDevice (LPDIRECT3DDEVICE9 device)
{
	SAVE_SET(this->device, device);
}

LPDIRECT3DDEVICE9 ceRenderController::GetDevice ()
{
	return device;
}

void ceRenderController::SetRenderSort (RenderSort sort)
{
	this->sort = sort;
}

RenderSort ceRenderController::GetRenderSort () const
{
	return sort;
}

void ceRenderController::SetCamera (const ceCamera* camera)
{
	SetViewMatrix (camera->GetMatrix ());
	SetProjectionMatrix (camera->GetProjector ()->GetMatrix ());
	renderEnv.cameraPosition = camera->GetFrom ();
}



void ceRenderController::SetProjectionMatrix (const D3DXMATRIX& matrix)
{
	matProj = matrix;
}

void ceRenderController::SetViewMatrix (const D3DXMATRIX& matrix)
{
	matView = matrix;
}

void ceRenderController::SetWorldMatrix (const D3DXMATRIX& matrix)
{
	matWorld = matrix;
}

void ceRenderController::SetLight (const D3DLIGHT9& light)
{
	this->light = light;
}

const D3DLIGHT9& ceRenderController::GetLight () const
{
	return light;
}

void ceRenderController::AddGeometryNode (ceGeometryNode* node, bool transparency)
{
	/*
	if (transparency || node->HasTransparency())
	{
		transparentNodes.push_back (node);
	}
	else
	*/
	{
		switch (sort)
		{
		case CERS_NoSort:
			nodes.push_back (node);
			break;
		case CERS_SortEffect:
			effectNodes [node->GetMaterial ()->GetEffect ()].push_back(node);
			break;
		case CERS_SortMaterial:
			materialNodes[node->GetMaterial ()].push_back(node);
			break;
		}
	}
}


void ceRenderController::Render (unsigned pass, const ceClipper* clipper)
{
	// update the rendering environment
	renderEnv.clipper = clipper;
	renderEnv.device = device;

	switch (sort)
	{
	case CERS_NoSort:
		renderNodes (nodes, pass, clipper);
		break;
	case CERS_SortEffect:
		renderEffectNodes (pass, clipper);
		break;
	case CERS_SortMaterial:
		renderMaterialNodes (pass, clipper);
		break;
	}

	renderNodes (transparentNodes, pass, clipper);
}



void ceRenderController::renderNodes (ceGeometryNodeList& nodes, unsigned pass, const ceClipper* clipper)
{
	ceGeometryNodeList::iterator it;
	for (it = nodes.begin (); it != nodes.end (); it++)
	{
		ceGeometryNodePtr node = *it;
		ceMaterial* material = node->GetMaterial ();
		iGeometry* geometry = node->GetGeometry();
		if (node->GetGeometryAnimator ())
		{
			node->GetGeometryAnimator ()->UpdateGeometry (geometry);
		}

		renderEnv.transform = &node->GetTransform ();

		material->Begin (device);
		material->BeginPass (pass);
		material->ApplyStates (device);
		applyGlobalEffectStates (material->GetEffect ());
		applyEffectStates (material->GetEffect (), material, node->GetTransform (), node->GetGeometryAnimator ());

		geometry->Render (renderEnv);
		material->EndPass ();
		material->End();
	}
}

void ceRenderController::renderEffectNodes (unsigned pass, const ceClipper* clipper)
{
	map<ceEffect*, ceGeometryNodeList>::iterator ite;
	for (ite = effectNodes.begin (); ite != effectNodes.end (); ite++)
	{
		ceEffect* effect = ite->first;
		ceGeometryNodeList& nodes = ite->second;
		effect->Begin ();
		effect->BeginPass (pass);
		applyGlobalEffectStates (effect);
		ceGeometryNodeList::iterator it;
		for (it = nodes.begin (); it != nodes.end (); it++)
		{
			ceGeometryNodePtr node = *it;
			ceMaterial* material = node->GetMaterial ();
			iGeometry* geometry = node->GetGeometry ();
			if (node->GetGeometryAnimator ())
			{
				node->GetGeometryAnimator ()->UpdateGeometry (geometry);
			}

			renderEnv.transform = &node->GetTransform ();

			material->ApplyStates (device);
			applyEffectStates (effect, material, node->GetTransform (), node->GetGeometryAnimator ());
			geometry->Render (renderEnv);
		}

		effect->EndPass ();
		effect->End ();
	}
}

void ceRenderController::renderMaterialNodes (unsigned pass, const ceClipper* clipper)
{
	unsigned count = 0;
	map<ceMaterial*, ceGeometryNodeList>::iterator itm;
	for (itm = materialNodes.begin (); itm != materialNodes.end (); itm++)
	{
		ceMaterial* material = itm->first;
		if (!material)
		{
			continue;
		}
		ceEffect* effect = material->GetEffect ();
		if (!effect)
		{
			continue;
		}
		ceGeometryNodeList& nodes = itm->second;
		material->Begin (device);
		material->BeginPass (pass);
		material->ApplyStates (device);

		applyGlobalEffectStates (effect);

		ceGeometryNodeList::iterator it;
		for (it = nodes.begin (); it != nodes.end (); it++)
		{
			ceGeometryNodePtr node = *it;
			iGeometry* geometry = node->GetGeometry ();
			if (node->GetGeometryAnimator ())
			{
				node->GetGeometryAnimator ()->UpdateGeometry (geometry);
			}

			renderEnv.transform = &node->GetTransform ();

			applyEffectStates (effect, material, node->GetTransform (), node->GetGeometryAnimator ());
			geometry->Render (renderEnv);
			count++;
		}

		material->EndPass ();
		material->End ();
	}
	//printf ("%d\n", count);
}


void ceRenderController::applyGlobalEffectStates (ceEffect* effect)
{
	if (!effect)
	{
		return;
	}

	effect->SetLight (light);
	effect->SetViewMatrix (matView);
	effect->SetProjMatrix (matProj);
}


void ceRenderController::applyEffectStates (ceEffect* effect, ceMaterial* material, ceTransform& transform, iGeometryAnimator* animator)
{
	if (!effect)
	{
		return;
	}

	effect->SetWorldMatrix (transform.GetMatrix ());
	if (animator)
	{
		animator->UpdateEffectStates (effect);
	}
	effect->GetEffect ()->CommitChanges ();
}

